import os
import time
import allure
import shutil

import cv2
import pyautogui
import pyperclip
import pytesseract
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.action_chains import ActionChains
from psa.log import logger
from settings import TESTDATA_DIR


class BaseElement(object):

    def __init__(self, driver: WebDriver):
        self.driver = driver

    def _find_element(self, locator, timeout=10) -> WebElement:
        logger.debug(f"Find Element By {locator}, TimeOut: {timeout}")
        element = WebDriverWait(self.driver, timeout).until(
            lambda x: x.find_element(*locator)
        )
        if element is None:
            logger.error(
                "Fail: Find Element. Locator:{}, TimeOut: {}".format(locator, timeout)
            )
            return None
        else:
            logger.debug("PASS: Find Element. Locator:{}".format(locator))
            return element

    def element_exists(self, locator, timeout=10, name="Element Exists Check") -> bool:
        logger.debug("Element Exists Check Start")

        @allure.step(name)
        def inner(locator, timeout=10):
            try:
                self._find_element(locator, timeout)
                logger.info("{}. Result: True".format(name))
                return True
            except:
                logger.info("{}. Result: False".format(name))
                return False

        inner(locator, timeout)
        logger.debug("Element Exists Check End")

    def wait_element(self, locator, timeout=10, name="Wait Element Displayed"):
        logger.debug("Wait Element Displayed Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            if element is None:
                raise Exception(
                    "Fail: {}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
                )
            else:
                logger.info(
                    "PASS: {}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
                )

        inner(locator, timeout)
        logger.debug("Wait Element Displayed End")

    def click_element(self, locator, timeout=10, name="Element Click"):
        logger.debug("Element Click Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            element.click()
            logger.info(f"PASS: {name}. Locator: {locator}")

        inner(locator, timeout)
        logger.debug("Element Click End")

    def click_element_with_offset(
        self,
        locator,
        xoffset=100,
        yoffset=100,
        timeout=10,
        name="Element Click With Offset",
    ):
        logger.debug("Element Click With Offset Start")

        @allure.step(name)
        def inner(locator, timeout, xoffset, yoffset):
            element = self._find_element(locator, timeout)
            action = ActionChains(self.driver)
            action.move_to_element(element)
            action.move_by_offset(xoffset, yoffset)
            action.click()
            action.perform()
            logger.info(
                f"PASS: {name}. Locator: {locator}, xoffset: {xoffset}, yoffset: {yoffset}"
            )

        inner(locator, timeout, xoffset=xoffset, yoffset=yoffset)
        logger.debug("Element Click With Offset End")

    def input_text(self, locator, text, timeout=10, name="Input Text To ELement"):
        logger.debug("Input Text To ELement Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            element.send_keys(text)
            logger.info(f"PASS: {name}. Locator: {locator}, Text: {text}")

        inner(locator, timeout)
        logger.debug("Input Text To ELement End")

    def get_text(self, locator, timeout=10, name="Get Element Text"):
        logger.debug("Get Element Text Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            text = element.text
            logger.info(f"PASS: {name}. Locator: {locator}, Text: {text}")
            return text

        return inner(locator, timeout)

    def clear_text(self, locator, timeout=10, name="Clear Element Text"):
        pass

    def get_attribute(
        self, locator, attribute, timeout=10, name="Get Element Attribute"
    ):
        logger.debug("Get Element Attribute Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            attr = element.get_attribute(attribute)
            logger.info(
                f"PASS: {name}. Locator: {locator}, Attribute: {attribute}, Value: {attr}"
            )
            return attr

        return inner(locator, timeout)

    def element_screenshot(
        self, locator, save_path=None, timeout=10, name="Element Screenshot"
    ):
        logger.debug("Element Screenshot Start")

        @allure.step(name)
        def inner(locator, timeout, save_path):
            element = self._find_element(locator, timeout)
            element.screenshot(save_path)
            logger.info(f"PASS: {name}. Locator: {locator}, Save Path: {save_path}")

        inner(locator, timeout, save_path)
        logger.debug("Element Screenshot End")

    def slide_element_to_view(self, locator, timeout=10, name="Slide Element To View"):
        logger.debug("Slide Element Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            ActionChains(self.driver).move_to_element(element).perform()
            logger.info(
                "PASS: {}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Slide Element End")

    def drag_and_drop(
        self, source_locator, target_locator, timeout=10, name="Drag And Drop"
    ):
        logger.debug("Drag And Drop Element Start")

        @allure.step(name)
        def inner(source_locator, target_locator, timeout=10):
            source_element = self._find_element(source_locator, timeout)
            target_element = self._find_element(target_locator, timeout)
            ActionChains(self.driver).drag_and_drop(
                source_element, target_element
            ).perform()
            logger.info(
                "PASS: {}. Source Locator: {}, Target Locator: {}".format(
                    name, source_locator, target_locator
                )
            )

        inner(source_locator, target_locator, timeout=10)
        logger.debug("Drag And Drop Element End")

    def drag_and_drop_by_offset(
        self,
        source_locator,
        xoffset,
        yoffset,
        timeout=10,
        name="Drag And Drop By Offset",
    ):
        logger.debug("Drag And Drop Element By Offset Start")

        @allure.step(name)
        def inner(source_locator, xoffset, yoffset, timeout=10):
            source_element = self._find_element(source_locator, timeout)
            ActionChains(self.driver).drag_and_drop_by_offset(
                source_element, xoffset=xoffset, yoffset=yoffset
            ).perform()
            logger.info(
                "PASS: {}. Locator: {}, xoffset: {}, yoffset: {}".format(
                    name, source_locator, xoffset, yoffset
                )
            )

        inner(source_locator, xoffset=xoffset, yoffset=yoffset, timeout=10)
        logger.debug("Drag And Drop Element By Offset End")

    def switch_to_iframe(self, locator, timeout=10, name="Switch To IFame By Element"):
        logger.debug("Switch To IFame By Element Start")

        @allure.step(name)
        def inner(locator, timeout=10):
            element = self._find_element(locator, timeout)
            self.driver.switch_to.frame(element)
            logger.info(
                "PASS: {}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Switch To IFame By Element End")

    def switch_to_top_iframe(self, name="Switch To Top IFame"):
        logger.debug("Switch To Top IFame Start")

        @allure.step(name)
        def inner():
            self.driver.switch_to.default_content()
            logger.info("PASS:{}.".format(name))

        inner()
        logger.debug("Switch To Top IFame End")

    def get_position(self, locator, timeout=10, name="Get Element Position"):
        @allure.step(name)
        def inner(locator, timeout=10):
            element = self._find_element(locator, timeout)
            position = element.location
            logger.info(
                "PASS: {}. Locator: {}, Position: {}".format(name, locator, position)
            )

        return inner(locator, timeout)

    def get_size(self, locator, timeout=10, name="Get Element Size"):
        @allure.step(name)
        def inner(locator, timeout=10):
            element = self._find_element(locator, timeout)
            size = element.size
            logger.info("PASS: {}. Locator: {}, Size: {}".format(name, locator, size))

        return inner(locator, timeout)

    def mouse_over(self, locator, timeout=10, name="Mouse Over"):
        logger.debug("Mouse Over Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            ActionChains(self.driver).move_to_element(element).perform()
            logger.info(
                "PASS:{} . Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Mouse Over End")

    def mouse_out(self, locator, timeout=10, name="Mouse Out"):
        logger.debug("Mouse Out Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            size = element.size
            x_offset = int(size["width"] / 2) + 1
            y_offset = int(size["height"] / 2) + 1
            ActionChains(self.driver).move_to_element(element).move_by_offset(
                x_offset, y_offset
            ).perform()
            logger.info(
                "PASS:{}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Mouse Out End")

    def mouse_down(self, locator, timeout=10, name="Mouse Down"):
        logger.debug("Mouse Down Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            ActionChains(self.driver).click_and_hold(element).perform()
            logger.info(
                "PASS: {}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Mouse Down End")

    def mouse_up(self, locator, timeout=10, name="Mouse Up"):
        logger.debug("Mouse Up Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            ActionChains(self.driver).release(element).perform()
            logger.info(
                "PASS:{}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Mouse Up End")

    def double_click(self, locator, timeout=10, name="Double Click Element"):
        logger.debug("Double Click Start")

        @allure.step(name)
        def inner(locator, timeout):
            element = self._find_element(locator, timeout)
            ActionChains(self.driver).double_click(element).perform()
            logger.info(
                "PASS: {}. Locator: {}, TimeOut: {}".format(name, locator, timeout)
            )

        inner(locator, timeout)
        logger.debug("Double Click End")

    def press_key(self, keys, timeout=10, name="Press Key"):
        logger.debug("Press Key Start")

        @allure.step(name)
        def inner(keys, presses, interval):
            pyautogui.press(keys, presses, interval)
            logger.info("PASS: {}. Keys: {}".format(name, keys))

        inner(keys)
        logger.debug("Press Key End")

    def input_by_copy(
        self, locator, text, timeout=10, name="Input Text To Element By Copy"
    ):
        logger.debug("Input Text To Element By Copy Start")

        @allure.step(name)
        def inner(text, locator, timeout):
            element = self._find_element(locator, timeout)
            element.click()
            pyperclip.copy(text)
            pyautogui.hotkey("ctrl", "v")
            logger.info("PASS: {}.Locator: {}, Text: {}".format(name, locator, text))

        inner(text, locator, timeout)
        logger.debug("Input Text To Element By Copy End")

    def input_by_keyboard(
        self, locator, text, interval=0.1, timeout=10, name="Input Text By Keyboard"
    ):
        logger.debug("Input Text By Keyboard Start")

        @allure.step(name)
        def inner(locator, text, interval, timeout):
            element = self._find_element(locator, timeout)
            element.click()
            pyautogui.write(text, interval=interval)
            logger.info(
                "PASS: {}.Locator: {}, Text: {}, interval: {}".format(
                    name, locator, text, interval
                )
            )

        inner(locator, text, interval, timeout)
        logger.debug("Input Text By Keyboard End")

    def upload_file(
        self,
        file_path,
        submit_locator=None,
        timeout=10,
        base_path="/home/kasm-user/Desktop",
        name="Upload File ",
    ):
        logger.debug("Upload File")

        @allure.step(name)
        def inner(submit_locator, file_path, timeout):
            if os.path.exists(file_path):

                if os.name == "nt":
                    logger.debug("@@@@ System Type: Windows")
                    pyperclip.copy(file_path)
                    time.sleep(3)
                    pyautogui.hotkey("ctrl", "v")
                    pyautogui.press("enter")
                    if submit_locator != None:
                        self.click_element(submit_locator, name="Upload File Submit")
                    logger.info("PASS:{}. File Path: {}".format(name, file_path))
                    time.sleep(3)
                elif os.name == "posix":
                    logger.debug("@@@@ System Type: Liunx")
                    file_name, file_extension = os.path.splitext(file_path)
                    file_name = file_name.split("/")[-1]
                    logger.debug("@@@@ file_name: {}".format(file_name))
                    desktop_path = ""
                    if os.path.exists(base_path):
                        desktop_path = "{}/{}{}".format(
                            base_path, file_name, file_extension
                        )
                    elif os.path.exists("/home/kasm-user/Desktop"):
                        desktop_path = "/home/kasm-user/Desktop/{}{}".format(
                            file_name, file_extension
                        )
                    shutil.copy(file_path, desktop_path)
                    logger.debug(
                        "Copy File To Desktop. Desktop Path:{}".format(desktop_path)
                    )

                    time.sleep(3)

                    # 上传窗口界面截图和保存文件名
                    image_name = "upload_image.jpg"
                    image_path = os.path.join(TESTDATA_DIR, image_name)
                    pyautogui.screenshot(image_path)

                    xy = self._get_xy_from_image(
                        image_path, desktop_name=file_extension
                    )
                    pyautogui.doubleClick(xy.get("x"), xy.get("y"))
                    if submit_locator != None:
                        self.click_element(submit_locator, name="Upload File Submit")
                    time.sleep(3)
                    logger.info("PASS:{}. File Path: {}".format(name, file_path))
                    os.remove(desktop_path)
                    os.remove(image_path)
                else:
                    logger.error("@@@@ unsupported OS")
                    raise OSError("Unsupported OS")
            else:
                logger.error("File Not Found. Path:{}".format(file_path))
                raise FileNotFoundError("File Not Found. Path:{}".format(file_path))

        inner(submit_locator, file_path, timeout)

    @staticmethod
    def _get_xy_from_image(image_path, desktop_name):
        pytesseract.pytesseract.tesseract_cmd = "/usr/bin/tesseract"
        image = cv2.imread(image_path)
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        data = pytesseract.image_to_data(gray, output_type=pytesseract.Output.DICT)
        xy = dict()
        for i in range(len(data["text"])):
            if desktop_name in data["text"][i]:
                logger.debug("@@@@@@@@@@@@@@@@@@@")
                x = int(int(data["left"][i]) + (int(data["width"][i]) / 2))
                y = int(int(data["top"][i]) + (int(data["height"][i]) / 2))
                logger.debug("**** center point: ({}, {})".format(x, y))
                logger.debug("@@@@@@@@@@@@@@@@@@@")
                xy["x"] = x
                xy["y"] = y
            else:
                continue
        logger.info("$$$$ xy: {}".format(xy))
        return xy
